package cn.edu.cug.cs.gtl.oi.utils.editors;

import java.awt.BorderLayout;
import java.awt.List;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;
import java.io.File;
import java.util.ArrayList;
import java.util.Vector;

import javax.swing.ButtonGroup;
import javax.swing.JCheckBoxMenuItem;
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;

import com.openinventor.inventor.SoDB;
import com.openinventor.inventor.SoInput;
import com.openinventor.inventor.SoPath;
import com.openinventor.inventor.SoPreferences;
import com.openinventor.inventor.actions.SoSearchAction;
import com.openinventor.inventor.nodes.SoMaterial;
import com.openinventor.inventor.nodes.SoNode;

/**
 * This class is used to choose an <b>SoMaterial</b> from palettes of predefined
 * materials (for example, gold, silver or bronze from the metal palette ;
 * emerald, pearl or ruby from the stones palette). The chosen material is
 * passed to callback functions registered with this component.
 *
 */
class MaterialList extends JFrame
{
  private static final String fileSep = System.getProperty("file.separator");

  private String materialDir;
  private java.util.List<Listener> materialListeners;
  private Vector<JCheckBoxMenuItem> menuItems;
  private Vector<String> mtlPalettes;
  private int curPalette;
  private JMenuBar menuBar;
  private JMenu menuPalette;
  private List paletteList;

  MaterialList()
  {
    this(null);
  }

  MaterialList(String dir)
  {
    super("Material List");

    if ( dir != null )
      materialDir = dir;
    else
    {
      materialDir = SoPreferences.getValue("OIV_MATERIALS_PATH");
      if ( materialDir == null )
      {
        String tmp = SoPreferences.getValue("OIVJHOME");

        StringBuffer pathBuffer = new StringBuffer();
        pathBuffer.append(tmp);
        pathBuffer.append(fileSep);
        pathBuffer.append("data");
        pathBuffer.append(fileSep);
        pathBuffer.append("materials");
        materialDir = pathBuffer.toString();
      }
    }
    materialListeners = new ArrayList<MaterialList.Listener>();
    curPalette = -1;
    menuItems = new Vector<JCheckBoxMenuItem>();
    mtlPalettes = new Vector<String>();

    // build the component tree
    buildGUI();

    // unmap on window manager close button
    WindowListener l = new WindowAdapter()
    {
      @Override
      public void windowClosing(WindowEvent e)
      {
        setVisible(false);
      }
    };
    addWindowListener(l);
  }

  /**
   * Adds the specified listener to receive material list's events.<br>
   * If listener l is null, no exception is thrown and no action is performed.
   *
   * @param listener
   *          the material list listener
   */
  void addListener(Listener listener)
  {
    if ( listener != null )
      materialListeners.add(listener);
  }

  /**
   * Removes the specified listener if present so that it no longer receives
   * editor's events.
   *
   * @param listener
   *          the editor listener
   * @return true if specified listener has been removed.
   */
  boolean removeListener(Listener listener)
  {
    return materialListeners.remove(listener);
  }

  private void invokeValueChanged(SoMaterial material)
  {
    for ( Listener listener : materialListeners )
      listener.valueChanged(material);
  }

  private void buildGUI()
  {
    if ( !setupPalettes() )
      return;

    menuBar = buildMenu();
    if ( curPalette == -1 )
      return;

    setLayout(new BorderLayout());
    setJMenuBar(menuBar);

    String[] table = createStringTable(new File(materialDir, mtlPalettes.elementAt(curPalette)));
    paletteList = new List(8, false);
    for ( int i = 0; i < table.length; i++ )
      paletteList.add(table[i]);
    paletteList.addItemListener(new ListPick());

    add(paletteList, BorderLayout.CENTER);
    pack();
  }

  private JMenuBar buildMenu()
  {
    JMenuBar menubar = new JMenuBar();
    menuPalette = new JMenu("Palettes");

    menuItems.removeAllElements();
    ButtonGroup groupPalette = new ButtonGroup();
    for ( int i = 0; i < mtlPalettes.size(); i++ )
    {
      JCheckBoxMenuItem gcbmi = new JCheckBoxMenuItem(mtlPalettes.elementAt(i));
      groupPalette.add(gcbmi);
      gcbmi.addItemListener(new MenuPick(i));
      menuItems.addElement(gcbmi);
      menuPalette.add(gcbmi);
    }
    if ( menuItems.size() == 0 )
      curPalette = -1;

    menubar.add(menuPalette);
    if ( curPalette != -1 )
      menuItems.elementAt(curPalette).setSelected(true);
    return menubar;
  }

  // Fill in the AWT list with file names from the current palette.
  private void fillInMaterialList()
  {
    if ( curPalette == -1 )
      return;

    String[] table = createStringTable(new File(materialDir, mtlPalettes.elementAt(curPalette)));
    paletteList.removeAll();
    for ( int i = 0; i < table.length; i++ )
      paletteList.add(table[i]);
  }

  // Get the dir name from the env variable, and set up the palettes.
  // This returns true if it did so without error, and sets the member
  // variable curPalette to 0. Else this returns false and sets
  // curPalette to -1.
  private boolean setupPalettes()
  {
    // see if SO_MATERIAL_DIR has changed.
    String envDir = SoPreferences.getValue("SO_MATERIAL_DIR");
    if ( envDir != null )
    {
      if ( envDir.equals(materialDir) && (curPalette != -1) )
        // env dir is same as current dir, palette is already OK
        return true;

      materialDir = envDir;
    }
    else if ( curPalette != -1 )
      // env dir not set, but palette is already OK
      return true;

    // read the palette !
    curPalette = 0;

    try
    {
      // build the list of directories
      File matDir = new File(materialDir);
      String[] files = matDir.list();
      if ( files == null )
        curPalette = -1;
      else
        for ( int i = 0; i < files.length; i++ )
        {
          File tmpFile = new File(matDir.getPath(), files[i]);
          if ( tmpFile.isDirectory() )
            mtlPalettes.addElement(files[i]);
        }
    }
    catch (SecurityException e)
    {
      curPalette = -1;
    }

    return (curPalette != -1);
  }

  private static String[] createStringTable(File dir)
  {
    try
    {
      if ( !dir.isDirectory() )
        return null;
      return dir.list();
    }
    catch (SecurityException e)
    {
      return null;
    }
  }

  /**
   * Defines an object which listens for material list's events.<br>
   * This object will be notified when material has changed.
   *
   */
  static class Listener
  {
    /**
     * Invoked when material has changed.
     *
     * @param material
     *          the new material
     */
    public void valueChanged(SoMaterial material)
    {};
  }

  class MenuPick implements ItemListener
  {
    int id;

    public MenuPick(int menuId)
    {
      id = menuId;
    }

    @Override
    public void itemStateChanged(ItemEvent e)
    {
      if ( id != curPalette )
      {
        curPalette = id;
        fillInMaterialList();
      }
    }
  }

  class ListPick implements ItemListener
  {
    @Override
    public void itemStateChanged(ItemEvent e)
    {
      String selection = paletteList.getSelectedItem();
      if ( selection.equals("<none>") )
        return;

      String filename = materialDir + fileSep + mtlPalettes.elementAt(curPalette) + fileSep + selection;

      // read the file, search for SoMaterial and pass data to callbacks
      SoInput in = new SoInput();
      SoNode root;
      if ( in.openFile(filename) )
      {
        root = SoDB.readNode(in);
        if ( root != null )
        {

          // find the material node
          SoSearchAction sa = new SoSearchAction();
          sa.setNodeClass(com.openinventor.inventor.nodes.SoMaterial.class, true);
          sa.apply(root);

          SoPath path = sa.getPath();

          if ( path != null )
          {
            // invoke the callbacks with this material !
            SoMaterial mtl = (SoMaterial) path.regular.getTail();
            invokeValueChanged(mtl);
          }
        }
      }
    }
  }
}
